package sunyu.controller.shiro;

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.DisabledAccountException;
import org.apache.shiro.authc.ExcessiveAttemptsException;
import org.apache.shiro.authc.ExpiredCredentialsException;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.LockedAccountException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authz.UnauthorizedException;
import org.apache.shiro.authz.annotation.Logical;
import org.apache.shiro.authz.annotation.RequiresAuthentication;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.apache.shiro.session.Session;
import org.apache.shiro.session.mgt.DefaultSessionKey;
import org.apache.shiro.session.mgt.SessionManager;
import org.apache.shiro.session.mgt.eis.SessionDAO;
import org.apache.shiro.subject.Subject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import sunyu.controller.common.CommonController;
import sunyu.controller.shiro.pojo.ShiroSession;
import sunyu.controller.shiro.pojo.ShiroSessions;
import sunyu.tools.common.JsonTools;
import sunyu.tools.encrypt.EncryptTools;

import javax.annotation.Resource;
import java.io.Serializable;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Map;

/**
 * shiro控制器
 *
 * @author 孙宇
 */
@Controller
@RequestMapping("/shiro")
public class ShiroController extends CommonController {

    private final Logger logger = LoggerFactory.getLogger(this.getClass());

    @Resource(name = "encryptTools")
    private EncryptTools encryptTools;

    @Resource(name = "shiroSessionDAO")
    private SessionDAO sessionDAO;

    @Resource(name = "webSessionManager")
    private SessionManager sessionManager;

    @Resource(name = "jsonTools")
    private JsonTools jsonTools;

    /**
     * 跳转到未经许可页面
     *
     * @return
     */
    @RequestMapping(value = "/error/unauthorized")
    public String unauthorized() {
        return "/shiro/error/unauthorized";
    }

    /**
     * 默认异常
     *
     * @return
     */
    @RequestMapping(value = "/error/defaultError")
    public String defaultError() {
        return "/shiro/error/defaultError";
    }

    /**
     * 退出登录
     *
     * @return
     */
    @RequestMapping(value = "/logout")
    public String logout() {
        Subject subject = SecurityUtils.getSubject();
        subject.logout();
        return "/index";
    }

    /**
     * 跳转到登录页面
     *
     * @return
     */
    @RequestMapping(value = "/login", method = {RequestMethod.GET})
    public String login() {
        return "/shiro/login";
    }

    /**
     * 登录
     *
     * @param loginName
     * @param loginPassword
     * @param model
     *
     * @return
     */
    @RequestMapping(value = "/login", method = {RequestMethod.POST})
    public String login(String loginName,
                        String loginPassword,
                        Model model) {
        String msg;
        Subject subject = SecurityUtils.getSubject();
        UsernamePasswordToken token = new UsernamePasswordToken(loginName, encryptTools.md5(loginPassword));
        try {
            subject.login(token);
            if (subject.isAuthenticated()) {
                return "redirect:/shiro/main";
            }
        } catch (IncorrectCredentialsException e) {
            msg = "[" + token.getPrincipal() + "]登录密码错误！";
            model.addAttribute("errMsg", msg);
            logger.error(msg);
        } catch (ExcessiveAttemptsException e) {
            msg = "登录失败次数过多！";
            model.addAttribute("errMsg", msg);
            logger.error(msg);
        } catch (LockedAccountException e) {
            msg = "[" + token.getPrincipal() + "]帐号已被锁定！";
            model.addAttribute("errMsg", msg);
            logger.error(msg);
        } catch (DisabledAccountException e) {
            msg = "[" + token.getPrincipal() + "]帐号已被禁用！";
            model.addAttribute("errMsg", msg);
            logger.error(msg);
        } catch (ExpiredCredentialsException e) {
            msg = "[" + token.getPrincipal() + "]帐号已过期！";
            model.addAttribute("errMsg", msg);
            logger.error(msg);
        } catch (UnknownAccountException e) {
            msg = "[" + token.getPrincipal() + "]帐号不存在！";
            model.addAttribute("errMsg", msg);
            logger.error(msg);
        } catch (UnauthorizedException e) {
            msg = "您没有得到相应的授权！[" + e.getMessage() + "]";
            model.addAttribute("errMsg", msg);
            logger.error(msg);
        }
        return "/shiro/login";
    }

    /**
     * 跳转到后台管理main页面
     *
     * @return
     */
    @RequestMapping(value = "/main", method = {RequestMethod.GET})
    @RequiresAuthentication//验证是否登录，等同于subject.isAuthenticated()
    @RequiresPermissions(value = {"shiro:main"}, logical = Logical.AND)//验证是否有权限
    public String main() {
        return "/shiro/main";
    }

    /**
     * 所有session列表
     *
     * @return
     */
    @RequestMapping(value = "/session/list", method = {RequestMethod.GET})
    @RequiresAuthentication
    @RequiresPermissions(value = {"shiro:session:list"}, logical = Logical.AND)
    public String sessionList(Model model) {
        ShiroSessions shiroSessions = new ShiroSessions();
        shiroSessions.setCurrentSessionId(SecurityUtils.getSubject().getSession().getId());
        Collection<Session> sessions = sessionDAO.getActiveSessions();
        for (Session session : sessions) {
            ShiroSession shiroSession = new ShiroSession();
            shiroSession.setSessionId(session.getId());
            String host = session.getHost();
            if (host.equals("0:0:0:0:0:0:0:1")) {
                host = "Localhost";
            }
            shiroSession.setHost(host);
            shiroSession.setTimeout(session.getTimeout());
            shiroSession.setStartTimestamp(session.getStartTimestamp());
            shiroSession.setLastAccessTime(session.getLastAccessTime());
            for (Object attributeKey : session.getAttributeKeys()) {
                Object attribute = session.getAttribute(attributeKey);
                shiroSession.getAttributes().add(new HashMap<String, Object>() {{
                    put(String.valueOf(attributeKey), attribute);
                }});
            }
            //System.out.println(jsonTools.toJson(session.getAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY)));
            //System.out.println(jsonTools.toJson(session.getAttribute(DefaultSubjectContext.AUTHENTICATED_SESSION_KEY)));
            //System.out.println(jsonTools.toJson(session.getAttribute(DefaultSubjectContext.SESSION_CREATION_ENABLED)));

            shiroSessions.getShiroSessionList().add(shiroSession);
        }

        Map<Serializable, ShiroSession> unique = new HashMap<>();//去重，在配置redis集群缓存的情况下，会出现重复数据，因为有两个节点有数据
        for (ShiroSession shiroSession : shiroSessions.getShiroSessionList()) {
            unique.put(shiroSession.getSessionId(), shiroSession);
        }
        shiroSessions.getShiroSessionList().clear();
        for (Map.Entry<Serializable, ShiroSession> entry : unique.entrySet()) {
            shiroSessions.getShiroSessionList().add(entry.getValue());
        }

        //按最后操作时间降序排列
        Collections.sort(shiroSessions.getShiroSessionList(), Collections.reverseOrder(new Comparator<ShiroSession>() {
            @Override
            public int compare(ShiroSession o1,
                               ShiroSession o2) {
                return o1.getLastAccessTime().compareTo(o2.getLastAccessTime());
            }
        }));

        shiroSessions.setTotal(shiroSessions.getShiroSessionList().size());

        model.addAttribute("shiroSessions", shiroSessions);
        return "/shiro/session/list";
    }

    /**
     * 踢出一个session
     *
     * @param sessionId
     *
     * @return
     */
    @RequestMapping(value = "/session/clear/{sessionId}")
    @RequiresAuthentication
    @RequiresPermissions(value = {"shiro:session:clear"})
    public String clearSession(@PathVariable("sessionId") String sessionId) {
        try {
            if (!SecurityUtils.getSubject().getSession().getId().equals(sessionId)) {
                DefaultSessionKey sessionKey = new DefaultSessionKey(sessionId);
                Session session = sessionManager.getSession(sessionKey);
                if (session != null) {
                    sessionDAO.delete(session);
                }
            }
        } catch (Exception e) {
            logger.error("踢出{}出错", sessionId);
        }
        return "redirect:/shiro/session/list";
    }

}
